<?php
/**
 * Класс-наследник класса проведения аутентификации пользователя.
 */
class Workset_Auth extends Zend_Auth implements Workset_Auth_Options_Interface {
	/**
	 * @var array
	 */
	protected $_aOptions = array();

	/**
	 * Задание параметров класса.
	 *
	 * @param array|Zend_Config $aOptions
	 */
	public function setOptions($aOptions) {
		if (!is_array($aOptions)) {
			if ($aOptions instanceof Zend_Config) {
				$aOptions = $aOptions->toArray();
			}
			else {
				throw new Workset_Acl_Exception(__METHOD__.' run with wrong param');
			}
		}

		$this->_aOptions = array_merge($this->_aOptions,$aOptions);
	}

	/**
	 * Получение списка параметров класса или значения конкретного параметра.
	 * 
	 * @param string $sName
	 * @return mixed
	 */
	public function getOptions($sName = '') {
		return $sName
			?( isset($this->_aOptions[$sName])
				? $this->_aOptions[$sName]
				: null
			): $this->_aOptions;
	}

	/**
	 * Получение объекта класса.
	 *
	 * @return Workset_Auth
	 */
	public static function getInstance() {
		if (null === self::$_instance) {
			self::$_instance = new self();
		}

		return self::$_instance;
	}

	/**
	 * Добавление обёртки для учёта запоминания пользователя на компьютере.
	 * 
	 * (non-PHPdoc)
	 * @see library/Zend/Zend_Auth#hasIdentity()
	 */
	public function hasIdentity() {
		$bResult = parent::hasIdentity();
		$aOptions = $this->getOptions();

		// Если в куках выставлен ключ запоминания пользователя на данном компьютере.
		if (!$bResult && isset($_COOKIE[$aOptions[self::OP_REMEMBER_COOKIE_NAME]])) {
			$aParts = explode(':',$_COOKIE[$aOptions[self::OP_REMEMBER_COOKIE_NAME]],4);
			list($sLogin,$sRememberKey,$sSign,$sTemp) = array_filter(array_map('trim',$aParts));

			// Необходимо для предотвращения зацикливания рекурсии.
			unset($_COOKIE[$aOptions[self::OP_REMEMBER_COOKIE_NAME]]);

			// Проверка корректности ключа восстановления аутентификации.
			if (strpos($sSign,dechex(crc32($aOptions[self::OP_AUTH_SECRET_KEY].":$sLogin:$sRememberKey"))) !== false
			&& $this->_compareRememberKey($sLogin,$sRememberKey,$aRecord)) {
				// Инициализация объекта адаптера Zend_Auth.
				$oAuthAdapter = new Zend_Auth_Adapter_DbTable($this->_getDbAdapter());

				$oAuthAdapter->setTableName($aOptions[self::OP_TABLE_USER]);
				$oAuthAdapter->setIdentityColumn($aOptions[self::OP_FIELD_USER_LOGIN]);
				$oAuthAdapter->setCredentialColumn($aOptions[self::OP_FIELD_USER_PASSWORD]);

				$oAuthAdapter->setIdentity($sLogin);
				$oAuthAdapter->setCredential($aRecord[$aOptions[self::OP_FIELD_USER_PASSWORD]]);
				
				// Проводим аутентификацию пользователя.
				$bResult = $this->authenticate($oAuthAdapter)->isValid();
			}

			// Если восстановили аутентификацию пользователя.
			if ($bResult) {
				$this->_afterAuthentication($sLogin,true);
			}
			// Очищаем куку запоминания пользователя на компьютере.
			else {
				setcookie($aOptions[self::OP_REMEMBER_COOKIE_NAME],false,time()-1,'/');
			}
		}

		return $bResult;
	}

	/**
	 * Проведение аутентификации.
	 * 
	 * @param string $sLogin
	 * @param string $sPassword
	 * @param boolean $bRemember
	 * @return boolean
	 */
	public function login($sLogin,$sPassword,$bRemember) {
		// Получение настроек.
		$aOptions = $this->getOptions();

		// Инициализация объекта адаптера Zend_Auth.
		if ($sPassword) {
			$oAuthAdapter = new Zend_Auth_Adapter_DbTable($this->_getDbAdapter());

			$oAuthAdapter->setTableName($aOptions[self::OP_TABLE_USER]);
			$oAuthAdapter->setIdentityColumn($aOptions[self::OP_FIELD_USER_LOGIN]);
			$oAuthAdapter->setCredentialColumn($aOptions[self::OP_FIELD_USER_PASSWORD]);

			$oAuthAdapter->setCredential($this->getPasswordHash($sLogin,$sPassword));
		}
		// ...если нет пароля, то значит используем OpenId адаптер. 
		else {
			$oRequest = Zend_Controller_Front::getInstance()->getRequest();
			$oAuthAdapter = new Zend_Auth_Adapter_OpenId();

			$oAuthAdapter->setReturnTo('http://'.$oRequest->getHttpHost().'/users/login/openid');
			$oAuthAdapter->setStorage(new Zend_OpenId_Consumer_Storage_File());
		}

		$oAuthAdapter->setIdentity($sLogin);

		try {
			if ($this->authenticate($oAuthAdapter)->isValid()) {
				if (!$sLogin && !$sLogin=$this->getIdentity()) {
					throw new Zend_Exception('ERROR: Login is empty! ('.__METHOD__.')');
				}

				$this->_afterAuthentication($sLogin,$bRemember);
				return true;
			}
			else {
				setcookie($aOptions[self::OP_REMEMBER_COOKIE_NAME],false,time()-1,'/');
				return false;
			}
		}
		catch (Exception $oException) {
			die($oException->getMessage());
		}
	}

	/**
	 * Получение хэша для пароля пользователя.
	 * 
	 * @param string $sLogin
	 * @param string $sPassword
	 * @return string
	 */
	public function getPasswordHash($sLogin,$sPassword) {
		$aOptions = $this->getOptions();

		return md5(join(':',array($sLogin,$aOptions[self::OP_AUTH_SECRET_KEY],$sPassword)));
	}

	/**
	 * Сравнение ключа запоминания пользователя.
	 * 
	 * @param string $sLogin
	 * @param string $sRememberKey
	 * @param array $aRecord
	 * @return boolean
	 */
	protected function _compareRememberKey($sLogin,$sRememberKey,&$aRecord = null) {
		return $this->_getRememberKey($sLogin,$aRecord) === $sRememberKey;
	}

	/**
	 * Получение адаптера БД.
	 * 
	 * @return Zend_Db_Adapter_Abstract
	 */
	protected function _getDbAdapter() {
		return Zend_Registry::get($this->getOptions(self::OP_REGISTRY_DB));
	}

	/**
	 * Получение ключа запоминания пользователя.
	 * 
	 * @param string $sLogin
	 * @return string
	 */
	protected function _getRememberKey($sLogin,&$aRecord = null) {
		// Получаем настройки ресурса.
		$aOptions = $this->getOptions();
		$aSpecial = array(); // Будет использоваться при ограничение количества запомненых машин.

		// Определение списка получаемых полей и порядка их объявления.
		$aFields = array(
			$aOptions[self::OP_FIELD_USER_SECRETWORD] => '',
			$aOptions[self::OP_FIELD_USER_LOGIN] => '',
			$aOptions[self::OP_FIELD_USER_PASSWORD] => '',
		);

		if ($aOptions[self::OP_REMEMBER_COUNT] == 1) {
			$aFields[$aOptions[self::OP_FIELD_USER_AUTHORIZED]] = '';
		}

		// Если нам не передана запись пользователя, то выполняем запрос к БД.
		if (!is_array($aRecord) || count(array_intersect_key($aFields,$aRecord)) != count($aFields)) {
			$oSelect = $this->_getDbAdapter()->select()
				->from($aOptions[self::OP_TABLE_USER],array_keys($aFields))
				->where($aOptions[self::OP_FIELD_USER_LOGIN].' = ?',$sLogin)
				->where(new Zend_Db_Expr("{$aOptions[self::OP_FIELD_USER_AUTHORIZED]} > "
					."FROM_UNIXTIME(UNIX_TIMESTAMP() - {$aOptions[self::OP_REMEMBER_TIME]})"));

			if (!$aRecord = $oSelect->query()->fetch()) {
				return false;
			}
		}

		// Вычисляем ключ для запоминания пользователя.
		return md5(join(':',array_merge($aSpecial,$aFields,array_intersect_key($aRecord,$aFields))));
	}

	/**
	 * Выполнение стандартных действий после аутентификации пользователя.
	 * 
	 * @param string $sLogin
	 * @param boolean $bRemember
	 */
	protected function _afterAuthentication($sLogin,$bRemember) {
		$aOptions = $this->getOptions();

		// Обновление времени последней аутентификации пользователя и IP пользователя.
		$aFields = array(
			$aOptions[self::OP_FIELD_USER_AUTHORIZED] => new Zend_Db_Expr('NOW()'),
			$aOptions[self::OP_FIELD_USER_AUTHORIZED_IP] => Zend_Controller_Front::getInstance()->getRequest()->getClientIp(),
		);
		$aWhere = array("{$aOptions[self::OP_FIELD_USER_LOGIN]} = ?" => $sLogin);

		if (!$this->_getDbAdapter()->update($aOptions[self::OP_TABLE_USER],$aFields,$aWhere)) {
			// Если не удалось произвести обновление по логину, то значит это регистрация по OpenId.
			$aFields[$aOptions[self::OP_FIELD_USER_LOGIN]] = $sLogin;
			$aFields[$aOptions[self::OP_FIELD_USER_PASSWORD]] = md5(microtime());
			$aFields[$aOptions[self::OP_FIELD_USER_SECRETWORD]] = md5(microtime());
			$aFields[$aOptions[self::OP_FIELD_USER_CREATED_IP]] = $aFields[$aOptions[self::OP_FIELD_USER_AUTHORIZED_IP]];

			if (!$this->_getDbAdapter()->insert($aOptions[self::OP_TABLE_USER],$aFields)) {
				throw new Zend_Auth_Exception('I can not put authentication date in DB');
			}
		}

		// Установка нового значения ключа автоматической авторизации.
		if ($bRemember) {
			$sRemember	= $this->_getRememberKey($sLogin);
			$sSign		= dechex(crc32($aOptions[self::OP_AUTH_SECRET_KEY].":$sLogin:$sRemember"));
			$sValue		= "$sLogin:$sRemember:".str_pad($sSign, 8, '0', STR_PAD_LEFT);
			$iLife		= time() + $aOptions[self::OP_REMEMBER_TIME];

			setcookie($aOptions[self::OP_REMEMBER_COOKIE_NAME],$sValue,$iLife,'/');
		}
		else {
			setcookie($aOptions[self::OP_REMEMBER_COOKIE_NAME],false,time()-1,'/');
		}
	}
}
?>